#include <iostream>
#include <assert.h>
#include "print.h"
#include "package.h"
#include "module.h"

Module::Module( Package& package, const std::string& name, const std::string& path ) :
   _package( package ),
   _errors(),
   _lexer( _errors ),
   _name( name ),
   _path( path )
{
}

bool Module::parse()
{
   std::cout << "Parsing " << _name << std::endl;

   if( _lexer.open( _path ) )
   {
      next_token();

      while( _current->type() != Token::T_EOF )
      {
         // FIX: immutable, interface, actor
         switch( _current->type() )
         {
         case Token::T_IMPORT:
            parse_import();
            break;

         case Token::T_ENUM:
            parse_enum();
            break;

         case Token::T_CLASS:
         case Token::T_IMMUTABLE:
         case Token::T_INTERFACE:
            parse_type_def();
            break;

         default:
            _errors.report( *_current ) << "Unexpected token '" << _current->string() << "' in module";
            next_token();
         }
      }

      _lexer.close();
   }

   _errors.print( _path );

   return _errors.success();
}

bool Module::resolve_base_types()
{
   std::cout << "Resolving base types in " << _name << "..." << std::endl;
   _errors.clear();

   // FIX: parse kits if necessary
   Package& root = _package.root();

   ASTImport::Vector::iterator vi = _imports.begin();
   ASTImport::Vector::iterator vend = _imports.end();

   for( ; vi != vend; ++vi )
   {
      ASTImport::Ptr import = *vi;
      ASTTypeDefinition::Vector list;

      root.resolve_type( *(import->name()), list );

      switch( list.size() )
      {
      case 0:
         _errors.report( *import ) << "import cannot be resolved to a type";
         break;

      case 1:
         import->set_type( list[0] );
         break;

      default:
         _errors.report( *import ) << "import is ambiguous";
      }
   }

   ASTTypeDefinition::Vector::iterator ti = _types.begin();
   ASTTypeDefinition::Vector::iterator tend = _types.end();

   for( ; ti != tend; ++ti )
   {
      ASTTypeDefinition::Ptr type = *ti;
      ASTTypeDefinition::Vector list;

      _package.resolve_local_type( type->name(), list );

      if( list.size() > 1 )
      {
         _errors.report( *type ) << "'" << type->name() << "' is multiply defined in package";
      }

      ASTType::Ptr base = type->base();

      if( base )
      {
         if( base->ref() != ASTType::REF_STRONG )
         {
            _errors.report( *type ) << "'" << type->name() << "' has invalid nil or weak base type";
         }

         // resolve base class
         ASTTypeDefinition::Vector base_list;
         resolve_type( *(base->base()), base_list );

         // base_list should have just 1 entry
         switch( base_list.size() )
         {
         case 0:
            _errors.report( *type ) << "'" << type->name() << "' cannot resolve its base class";
            break;

         case 1:
            type->set_base_def( base_list[0] );
            break;

         default:
            _errors.report( *type ) << "'" << type->name() << "' has an ambiguous base class";
         }
      } else {
         // FIX: assign Object as the base class
      }
   }

   _errors.print( _path );

   return _errors.success();
}

bool Module::type_check()
{
   std::cout << "Type checking " << _name << "..." << std::endl;
   _errors.clear();

   ASTTypeDefinition::Vector::iterator i = _types.begin();
   ASTTypeDefinition::Vector::iterator end = _types.end();

   for( ; i != end; ++i )
   {
      (*i)->shadow_check( _errors );
      (*i)->type_check( *this, _errors );
   }

   _errors.print( _path );

   return _errors.success();
}

void Module::resolve_type( const ASTBaseType& type, ASTTypeDefinition::Vector& list )
{
   if( type.count() == 1 )
   {
      // unqualified name
      const std::string& name = type.get( 0 );

      // check types in the local package, including this module
      _package.resolve_local_type( name, list );

      // check imported types
      ASTImport::Vector::iterator i = _imports.begin();
      ASTImport::Vector::iterator end = _imports.end();

      for( ; i != end; ++i )
      {
         if( (*i)->match( name ) )
         {
            list.push_back( (*i)->type() );
         }
      }

      // FIX: check built-in types (U32, etc) - treat built-ins as a kit?
   } else {
      // qualified name

      // check root package
      _package.root().resolve_type( type, list );

      // FIX: package imports? kits?
   }
}

void Module::resolve_local_type( const std::string& name, ASTTypeDefinition::Vector& list )
{
   ASTTypeDefinition::Vector::iterator i = _types.begin();
   ASTTypeDefinition::Vector::iterator end = _types.begin();

   for( ; i != end; ++i )
   {
      if( (*i)->name() == name )
      {
         list.push_back( *i );
      }
   }
}

void Module::print( uint32_t level )
{
   indent( level );
   std::cout << "module " << _name << std::endl;
   print_vector( _imports, level + 1 );
   print_vector( _types, level + 1 );
}

void Module::next_token()
{
   _current = _lexer.next();
}

void Module::parse_import()
{
   // FIX: package import? wildcard import?

   // consume import
   next_token();

   ASTBaseType::Ptr base_type = parse_base_type();

   if( !base_type )
   {
      return;
   }

   ASTImport::Ptr ast = ASTImport::Ptr( new ASTImport( base_type ) );
   _imports.push_back( ast );

   if( _current->type() == Token::T_TERMINATOR )
   {
      // consume ;
      next_token();
   } else {
      _errors.report( *_current ) << "Expected ; after type in import statement";
   }
}

void Module::parse_enum()
{
   // consume enum
   next_token();

   if( _current->type() != Token::T_TYPE )
   {
      _errors.report( *_current ) << "Expected type name in enum definition";
      return;
   }

   // consume name
   ASTEnum::Ptr ast = ASTEnum::Ptr( new ASTEnum( *_current ) );
   _types.push_back( ast );
   next_token();

   // optional includes
   if( _current->type() == Token::T_INCLUDES )
   {
      next_token();
      ast->set_base( parse_type() );
   }

   // consume {
   if( _current->type() != Token::T_LBRACE )
   {
      _errors.report( *_current ) << "Expected { after enum declaration";
      return;
   }

   next_token();

   // optional identifier list
   if( _current->type() == Token::T_IDENTIFIER )
   {
      ASTIdentifier::Ptr id = ASTIdentifier::Ptr( new ASTIdentifier( *_current ) );
      ast->append( id );
      next_token();

      while( _current->type() == Token::T_COMMA )
      {
         next_token();

         if( _current->type() != Token::T_IDENTIFIER )
         {
            _errors.report( *_current ) << "Expected identifier in enum definition";
            return;
         }

         id = ASTIdentifier::Ptr( new ASTIdentifier( *_current ) );
         ast->append( id );
         next_token();
      }
   }

   if( _current->type() != Token::T_RBRACE )
   {
      _errors.report( *_current ) << "Expected } in enum definition";
      return;
   }

   // consume }
   next_token();
}

void Module::parse_type_def()
{
   // consume phylum
   ASTClass::phylum_t phylum = ASTClass::PHYLUM_UNKNOWN;

   switch( _current->type() )
   {
   case Token::T_CLASS:
      phylum = ASTClass::PHYLUM_CLASS;
      break;

   case Token::T_IMMUTABLE:
      phylum = ASTClass::PHYLUM_IMMUTABLE;
      break;

   case Token::T_INTERFACE:
      phylum = ASTClass::PHYLUM_INTERFACE;
      break;

   default:
      assert( 0 );
   }

   next_token();

   if( _current->type() != Token::T_TYPE )
   {
      _errors.report( *_current ) << "Expected type name in class definition";
      return;
   }

   // consume name
   ASTClass::Ptr ast = ASTClass::Ptr( new ASTClass( phylum, *_current ) );
   _types.push_back( ast );
   next_token();

   // generic formal parameters
   if( _current->type() == Token::T_LANGLE )
   {
      parse_generics( ast );
   }

   // extends
   if( _current->type() == Token::T_EXTENDS )
   {
      if( phylum == ASTClass::PHYLUM_INTERFACE )
      {
         _errors.report( *_current ) << "Interface can't extend a type";
      }

      next_token();
      ast->set_base( parse_type() );
   }

   // implements
   if( _current->type() == Token::T_IMPLEMENTS )
   {
      parse_implements( ast );
   }

   // generic constraints
   // FIX: interfaces can't have generic constraints... why not?
   // FIX: co/contravariant interface generics?
   while( _current->type() == Token::T_WHERE )
   {
      parse_generic_constraint( ast );
   }

   // consume {
   if( _current->type() != Token::T_LBRACE )
   {
      _errors.report( *_current ) << "Expected { after class declaration";
      return;
   }

   next_token();

   if( _current->type() != Token::T_RBRACE )
   {
      while( _current->type() != Token::T_RBRACE )
      {
         // FIX: inner types, delegation
         switch( _current->type() )
         {
         case Token::T_LITERAL:
            ast->add_literal( parse_literal() );
            break;

         case Token::T_TYPE:
            // FIX: interface can't have members
            ast->add_member( parse_member() );
            break;

         case Token::T_PUBLIC:
         case Token::T_PACKAGE:
         case Token::T_PROTECTED:
            // FIX: interface methods cannot have a body
            ast->add_method( parse_method() );
            break;

         default:
            _errors.report( *_current ) << "Unexpected token '" << _current->string() << "' in class definition";
            return;
         }
      }
   }

   if( _current->type() != Token::T_RBRACE )
   {
      _errors.report( *_current ) << "Expected } in class definition";
      return;
   }

   // consume }
   next_token();
}

void Module::parse_generics( ASTClass::Ptr ast )
{
   // consume <
   next_token();

   while( _current->type() == Token::T_TYPE )
   {
      ASTGeneric::Ptr generic = ASTGeneric::Ptr( new ASTGeneric( *_current ) );
      ast->add_generic( generic );
      next_token();

      if( _current->type() != Token::T_COMMA )
      {
         break;
      }

      next_token();
   }

   // consume >
   if( _current->type() != Token::T_RANGLE )
   {
      _errors.report( *_current ) << "Expected > after generic formal parameters";
      return;
   }

   next_token();
}

void Module::parse_implements( ASTClass::Ptr ast )
{
   // consume implements
   next_token();

   while( ASTType::Ptr base = parse_type() )
   {
      ast->add_implements( base );

      if( _current->type() != Token::T_COMMA )
      {
         break;
      }

      next_token();
   }
}

void Module::parse_generic_constraint( ASTClass::Ptr ast )
{
   // consume where
   next_token();

   if( _current->type() != Token::T_TYPE )
   {
      _errors.report( *_current ) << "Expected generic formal parameter name after where";
      return;
   }

   // consume generic name
   ASTGeneric::Ptr generic = ast->generic( _current->string() );

   if( !generic )
   {
      _errors.report( *_current ) << "Undefined generic formal parameter '" << _current->string() << "'";
   }

   next_token();

   // consume :
   if( _current->type() != Token::T_TYPE_SEPARATOR )
   {
      _errors.report( *_current ) << "Expected : after generic formal parameter in where clause";
      return;
   }

   next_token();

   // consume constraints
   while( ASTType::Ptr type = parse_type() )
   {
      if( generic )
      {
         generic->add_constraint( type );
      }

      if( _current->type() != Token::T_COMMA )
      {
         break;
      }

      next_token();
   }
}

ASTBaseType::Ptr Module::parse_base_type()
{
   if( _current->type() != Token::T_TYPE )
   {
      _errors.report( *_current ) << "Expected type in base type";
      return ASTBaseType::Ptr();
   }

   ASTBaseType::Ptr ast = ASTBaseType::Ptr( new ASTBaseType( *_current ) );
   next_token();

   while( _current->type() == Token::T_TYPE_SEPARATOR )
   {
      next_token();

      if( _current->type() != Token::T_TYPE )
      {
         _errors.report( *_current ) << "Expected type after type separator in base type";
         break;
      }

      ast->append( *_current );
      next_token();
   }

   return ast;
}

ASTType::Ptr Module::parse_type()
{
   ASTBaseType::Ptr base = parse_base_type();

   if( !base )
   {
      return ASTType::Ptr();
   }

   ASTType::Ptr ast = ASTType::Ptr( new ASTType( base ) );

   if( _current->type() == Token::T_LANGLE )
   {
      next_token();

      while( true )
      {
         ASTType::Ptr parameter = parse_type();

         if( !parameter )
         {
            _errors.report( *_current ) << "Couldn't parse type in generic formal parameters";
            break;
         }

         ast->bind( parameter );

         // consume , or we're done
         if( _current->type() != Token::T_COMMA )
         {
            break;
         }

         next_token();
      }

      if( _current->type() != Token::T_RANGLE )
      {
         _errors.report( *_current ) << "Expected > to close generic formal parameters";
         return ast;
      }

      next_token();
   }

   if( _current->type() == Token::T_STAR )
   {
      ast->set_ref( ASTType::REF_NIL );
      next_token();
   } else if( _current->type() == Token::T_WEAK ) {
      ast->set_ref( ASTType::REF_WEAK );
      next_token();
   }

   return ast;
}

ASTLiteral::Ptr Module::parse_literal()
{
   // consume literal
   next_token();

   if( _current->type() != Token::T_IDENTIFIER )
   {
      _errors.report( *_current ) << "Expected identifier after literal declaration";
      return ASTLiteral::Ptr();
   }

   ASTIdentifier::Ptr id = ASTIdentifier::Ptr( new ASTIdentifier( *_current ) );
   ASTLiteral::Ptr ast = ASTLiteral::Ptr( new ASTLiteral( id ) );
   next_token();

   // consume =
   if( _current->type() != Token::T_ASSIGN )
   {
      _errors.report( *_current ) << "Expected = after literal identifier";
      return ast;
   }

   next_token();

   // attach value
   ast->set_value( parse_expr() );

   // consume ;
   if( _current->type() != Token::T_TERMINATOR )
   {
      _errors.report( *_current ) << "Expected ; after literal definition";
      return ast;
   }

   next_token();

   return ast;
}

ASTMember::Ptr Module::parse_member()
{
   ASTType::Ptr type = parse_type();

   if( !type )
   {
      _errors.report( *_current ) << "Expected type in member declaration";
      return ASTMember::Ptr();
   }

   ASTMember::Ptr ast = ASTMember::Ptr( new ASTMember( type ) );

   if( _current->type() != Token::T_MEMBER )
   {
      _errors.report( *_current ) << "Expected member identifier after type in member declaration";
      return ast;
   }

   ASTIdentifier::Ptr id = ASTIdentifier::Ptr( new ASTIdentifier( *_current ) );
   next_token();

   ast->set_identifier( id );

   if( _current->type() != Token::T_TERMINATOR )
   {
      _errors.report( *_current ) << "Expected ; after member definition";
      return ast;
   }

   next_token();

   return ast;
}

ASTMethod::Ptr Module::parse_method()
{
   // consume visibility
   ASTMethod::Ptr ast = ASTMethod::Ptr( new ASTMethod( *_current ) );
   next_token();

   // FIX: override?
   if( _current->type() == Token::T_NEW )
   {
      ast->set_constructor( true );
      next_token();
   }

   if( _current->type() != Token::T_IDENTIFIER )
   {
      _errors.report( *_current ) << "Expected identifier in method declaration";
      return ast;
   }

   ASTIdentifier::Ptr id = ASTIdentifier::Ptr( new ASTIdentifier( *_current ) );
   ast->set_identifier( id );
   next_token();

   ASTTypeIdentifier::VectorPtr params = parse_param_list();

   if( !params )
   {
      _errors.report( *_current ) << "Expected parameter list in method declaration";
      return ast;
   }

   ast->set_params( params );

   if( !ast->constructor() )
   {
      if( _current->type() == Token::T_RESULTS )
      {
         next_token();
         ASTTypeIdentifier::VectorPtr results = parse_param_list();

         if( !results )
         {
            _errors.report( *_current ) << "Expected result list in method declaration";
            return ast;
         }

         ast->set_results( results );
      }
   }

   if( ast->constructor() )
   {
      if( _current->type() == Token::T_EXTENDS )
      {
         ASTCall::Ptr extends;
         next_token();

         if( _current->type() == Token::T_SUPER )
         {
            extends = parse_super_call();
         } else if( _current->type() == Token::T_IDENTIFIER ) {
            ASTIdentifier::Ptr id = ASTIdentifier::Ptr( new ASTIdentifier( *_current ) );
            next_token();
            extends = parse_call( id );
         }

         if( !extends )
         {
            _errors.report( *_current ) << "Expected constructor chain";
            return ast;
         }

         ast->set_extends( extends );
      }
   }

   if( _current->type() == Token::T_THROWS )
   {
      next_token();

      while( true )
      {
         ASTType::Ptr type = parse_type();

         if( !type )
         {
            _errors.report( *_current ) << "Expected type in method throws list";
            break;
         }

         ast->add_throws( type );

         if( _current->type() != Token::T_COMMA )
         {
            break;
         }

         next_token();
      }
   }

   if( _current->type() == Token::T_TERMINATOR )
   {
      return ast;
   }

   if( _current->type() != Token::T_LBRACE )
   {
      _errors.report( *_current ) << "Expected { to open method body";
      return ast;
   }

   ASTBlock::Ptr body = parse_block();

   if( !body )
   {
      _errors.report( *_current ) << "Expected method body";
      return ast;
   }

   ast->set_body( body );
   return ast;
}

ASTBlock::Ptr Module::parse_block()
{
   ASTBlock::Ptr ast = ASTBlock::Ptr( new ASTBlock( *_current ) );

   // consume {
   next_token();

   if( _current->type() != Token::T_RBRACE )
   {
      while( true )
      {
         ASTCommand::Ptr command = parse_command();

         if( !command )
         {
            _errors.report( *_current ) << "Couldn't parse command in block";
            break;
         }

         ast->add_command( command );

         if( _current->type() == Token::T_RBRACE )
         {
            break;
         }
      }
   }

   // FIX: catch blocks, finally block

   // consume }
   next_token();

   return ast;
}

ASTCommand::Ptr Module::parse_command()
{
   /*
    * FIX: commands
    * assert( expression )
    * debug( command_list )
    * when
    * switch
    * for
    * while
    * break
    * continue
    * throw
    */

   ASTCommand::Ptr ast;

   switch( _current->type() )
   {
   case Token::T_LBRACE:
      ast = parse_block();
      break;

   case Token::T_VAR:
      ast = parse_variable();
      break;

   case Token::T_IF:
      ast = parse_if();
      break;

   case Token::T_RETURN:
      ast = ASTReturn::Ptr( new ASTReturn( *_current ) );
      next_token();

      // consume ;
      if( _current->type() != Token::T_TERMINATOR )
      {
         _errors.report( *_current ) << "Expected ; after return";
      } else {
         next_token();
      }
      break;

   default:
      {
         ASTExpr::VectorPtr lhs = parse_expr_list();

         if( !lhs )
         {
            _errors.report( *_current ) << "Expected command";
            break;
         }

         if( _current->type() == Token::T_ASSIGN )
         {
            next_token();

            ASTAssign::Ptr assign = ASTAssign::Ptr( new ASTAssign( lhs ) );
            ast = assign;

            ASTExpr::VectorPtr rhs = parse_expr_list();

            if( !rhs )
            {
               _errors.report( *_current ) << "Expected expression list after = in command";
               break;
            }

            assign->set_rhs( rhs );
         } else {
            ast = ASTExprCommand::Ptr( new ASTExprCommand( lhs ) );
         }

         if( _current->type() != Token::T_TERMINATOR )
         {
            _errors.report( *_current ) << "Expected terminator after expression";
            return ast;
         }

         next_token();
      }
   }

   return ast;
}

ASTVariable::Ptr Module::parse_variable()
{
   // consume var
   next_token();

   ASTType::Ptr type = parse_type();

   if( !type )
   {
      _errors.report( *_current ) << "Expected type in variable declaration";
      return ASTVariable::Ptr();
   }

   ASTVariable::Ptr ast = ASTVariable::Ptr( new ASTVariable( type ) );

   while( _current->type() == Token::T_IDENTIFIER )
   {
      ASTIdentifier::Ptr id = ASTIdentifier::Ptr( new ASTIdentifier( *_current ) );
      ast->add_identifier( id );
      next_token();

      if( _current->type() != Token::T_COMMA )
      {
         break;
      }

      next_token();
   }

   // optional initialisation
   if( _current->type() == Token::T_ASSIGN )
   {
      next_token();

      ASTExpr::VectorPtr list = parse_expr_list();

      if( !list )
      {
         _errors.report( *_current ) << "Expected expression list after = in variable declaration";
         return ast;
      }

      ast->set_initialiser( list );
   }

   // consume ;
   if( _current->type() != Token::T_TERMINATOR )
   {
      _errors.report( *_current ) << "Expected ; after variable declaration";
      return ast;
   }

   next_token();

   return ast;
}

ASTIf::Ptr Module::parse_if()
{
   // consume if
   next_token();

   // consume (
   if( _current->type() != Token::T_LPAREN )
   {
      _errors.report( *_current ) << "Expect ( after if";
      return ASTIf::Ptr();
   }

   next_token();

   // consume conditional expression
   ASTExpr::Ptr condition = parse_expr();

   if( !condition )
   {
      _errors.report( *_current ) << "Expected expression after if";
      return ASTIf::Ptr();
   }

   ASTIf::Ptr ast = ASTIf::Ptr( new ASTIf( condition ) );

   // consume )
   if( _current->type() != Token::T_RPAREN )
   {
      _errors.report( *_current ) << "Expected ) after if conditional";
      return ast;
   }

   next_token();

   if( _current->type() != Token::T_LBRACE )
   {
      _errors.report( *_current ) << "Expected { after if";
      return ast;
   }

   ASTBlock::Ptr on_true = parse_block();

   if( !on_true )
   {
      _errors.report( *_current ) << "Expected block after if";
      return ast;
   }

   ast->set_true( on_true );

   if( _current->type() == Token::T_ELSE )
   {
      next_token();
      ASTBlock::Ptr on_false = parse_block();

      if( !on_false )
      {
         _errors.report( *_current ) << "Expected block after else";
         return ast;
      }

      ast->set_false( on_false );
   }

   return ast;
}

ASTExpr::Ptr Module::parse_expr()
{
   ASTExpr::Ptr ast = parse_base_expr();

   if( !ast )
   {
      return ASTExpr::Ptr();
   }

   if( is_binary_op() )
   {
      ast = parse_binary_expr( ast );
   }

   // FIX: conv, fold, range, ternary
   return ast;
}

ASTExpr::Ptr Module::parse_paren_expr()
{
   // consume (
   next_token();

   ASTExpr::Ptr ast = parse_expr();

   if( !ast )
   {
      _errors.report( *_current ) << "Couldn't parse expression in parenthetical expression";
      return ASTExpr::Ptr();
   }

   // consume )
   if( _current->type() != Token::T_RPAREN )
   {
      _errors.report( *_current ) << "Expected ) to close parenthetical expression";
      return ast;
   }

   next_token();

   return ast;
}

ASTArray::Ptr Module::parse_array()
{
   ASTArray::Ptr ast = ASTArray::Ptr( new ASTArray( *_current ) );

   // consume [
   next_token();

   ASTExpr::VectorPtr list = parse_expr_list();

   if( !list )
   {
      _errors.report( *_current ) << "Couldn't parse expression list in array literal";
      return ast;
   }

   ast->set_list( list );

   // consume ]
   if( _current->type() != Token::T_RBRACKET )
   {
      _errors.report( *_current ) << "Expected ] to close array literal";
      return ast;
   }

   next_token();

   return ast;
}

ASTUnary::Ptr Module::parse_unary_expr()
{
   ASTUnary::Ptr unary = ASTUnary::Ptr( new ASTUnary( *_current ) );
   next_token();

   unary->set_rhs( parse_base_expr() );

   return unary;
}

ASTBinary::Ptr Module::parse_binary_expr( ASTExpr::Ptr lhs )
{
   ASTBinary::Ptr binary = ASTBinary::Ptr( new ASTBinary( *_current, lhs ) );
   next_token();

   ASTExpr::Ptr rhs = parse_base_expr();

   if( !rhs )
   {
      return binary;
   }

   binary->set_rhs( rhs );

   if( is_binary_op() )
   {
      binary = parse_binary_expr( binary );
   }

   return binary;
}

ASTExpr::Ptr Module::parse_base_expr()
{
   ASTExpr::Ptr ast;

   // FIX: conv, fold, map literal, cast

   switch( _current->type() )
   {
   case Token::T_TRUE:
   case Token::T_FALSE:
      ast = ASTBoolean::Ptr( new ASTBoolean( *_current ) );
      next_token();
      break;

   case Token::T_BINARY:
   case Token::T_INTEGER:
   case Token::T_HEXADECIMAL:
      ast = ASTInteger::Ptr( new ASTInteger( *_current ) );
      next_token();
      break;

   case Token::T_REAL:
      ast = ASTReal::Ptr( new ASTReal( *_current ) );
      next_token();
      break;

   case Token::T_IMAGINARY:
      ast = ASTImaginary::Ptr( new ASTImaginary( *_current ) );
      next_token();
      break;

   case Token::T_STRING:
      ast = ASTString::Ptr( new ASTString( *_current ) );
      next_token();
      break;

   case Token::T_LPAREN:
      ast = parse_paren_expr();
      break;

   case Token::T_LBRACKET:
      ast = parse_array();
      break;

   case Token::T_PLUS:
   case Token::T_MINUS:
   case Token::T_NOT:
      ast = parse_unary_expr();
      break;

   case Token::T_THIS:
      ast = ASTThis::Ptr( new ASTThis( *_current ) );
      next_token();
      break;

   case Token::T_IDENTIFIER:
   case Token::T_MEMBER:
      ast = ASTIdentifier::Ptr( new ASTIdentifier( *_current ) );
      next_token();
      break;

   case Token::T_SUPER:
      ast = parse_super_call();
      break;

   case Token::T_TYPE:
      ast = parse_type_member();
      break;

   default:
      _errors.report( *_current ) << "Unexpected token '" << _current->string() << "', expecting an expression";
      return ASTExpr::Ptr();
   }

   switch( _current->type() )
   {
   case Token::T_LPAREN:
      ast = parse_call( ast );
      break;

   case Token::T_DOT:
      ast = parse_object_call( ast );
      break;

   case Token::T_LBRACKET:
      ast = parse_array_index( ast );
      break;

   default:
      break;
   }

   return ast;
}

ASTArrayIndex::Ptr Module::parse_array_index( ASTExpr::Ptr array )
{
   // consume [
   next_token();

   ASTArrayIndex::Ptr ast = ASTArrayIndex::Ptr( new ASTArrayIndex( array, parse_expr_list() ) );

   if( _current->type() != Token::T_RBRACKET )
   {
      _errors.report( *_current ) << "Expected ] to end array index";
      return ast;
   }

   // consume ]
   next_token();

   return ast;
}

ASTCall::Ptr Module::parse_call( ASTExpr::Ptr method )
{
   ASTCall::Ptr ast = ASTCall::Ptr( new ASTCall( method ) );
   ast->set_args( parse_arg_list() );

   return ast;
}

ASTSuperCall::Ptr Module::parse_super_call()
{
   // consume super
   next_token();

   // consume .
   if( _current->type() != Token::T_DOT )
   {
      _errors.report( *_current ) << "Expected . after super";
      return ASTSuperCall::Ptr();
   }

   next_token();

   // method name
   if( _current->type() != Token::T_IDENTIFIER )
   {
      _errors.report( *_current ) << "Expected method name after super .";
      return ASTSuperCall::Ptr();
   }

   ASTIdentifier::Ptr method = ASTIdentifier::Ptr( new ASTIdentifier( *_current ) );
   ASTSuperCall::Ptr ast = ASTSuperCall::Ptr( new ASTSuperCall( method ) );
   next_token();

   ast->set_args( parse_arg_list() );

   return ast;
}

ASTObjectCall::Ptr Module::parse_object_call( ASTExpr::Ptr object )
{
   // consume .
   next_token();

   // method name
   if( _current->type() != Token::T_IDENTIFIER )
   {
      _errors.report( *_current ) << "Expected method name after .";
      return ASTObjectCall::Ptr();
   }

   ASTIdentifier::Ptr method = ASTIdentifier::Ptr( new ASTIdentifier( *_current ) );
   ASTObjectCall::Ptr ast = ASTObjectCall::Ptr( new ASTObjectCall( object, method ) );
   next_token();

   ast->set_args( parse_arg_list() );

   return ast;
}

ASTTypeIdentifier::Ptr Module::parse_type_member()
{
   ASTType::Ptr type = parse_type();

   if( !type )
   {
      return ASTTypeIdentifier::Ptr();
   }

   ASTTypeIdentifier::Ptr ast = ASTTypeIdentifier::Ptr( new ASTTypeIdentifier( type ) );

   // consume .
   if( _current->type() != Token::T_DOT )
   {
      _errors.report( *_current ) << "Expected . after bound type in expression";
      return ast;
   }

   next_token();

   // consume identifier
   if( _current->type() != Token::T_IDENTIFIER )
   {
      _errors.report( *_current ) << "Expected identifier after bound type . in expression";
      return ast;
   }

   ASTIdentifier::Ptr id = ASTIdentifier::Ptr( new ASTIdentifier( *_current ) );
   next_token();

   ast->set_identifier( id );
   return ast;
}

ASTExpr::VectorPtr Module::parse_expr_list()
{
   ASTExpr::VectorPtr list;

   while( true )
   {
      ASTExpr::Ptr expr = parse_expr();

      if( !expr )
      {
         _errors.report( *_current ) << "Couldn't parse expression in list";
         break;
      }

      if( !list )
      {
         list = ASTExpr::VectorPtr( new ASTExpr::Vector() );
      }

      list->push_back( expr );

      // consume , or we're done
      if( _current->type() != Token::T_COMMA )
      {
         break;
      }

      next_token();
   }

   return list;
}

ASTExpr::VectorPtr Module::parse_arg_list()
{
   // consume (
   if( _current->type() != Token::T_LPAREN )
   {
      _errors.report( *_current ) << "Expected ( to open argument list";
      return ASTExpr::VectorPtr();
   }

   next_token();

   // check for an empty set
   ASTExpr::VectorPtr list;

   if( _current->type() != Token::T_RPAREN )
   {
      list = parse_expr_list();
   } else {
      list = ASTExpr::VectorPtr( new ASTExpr::Vector() );
   }

   // consume )
   if( _current->type() != Token::T_RPAREN )
   {
      _errors.report( *_current ) << "Expected ) to close argument list";
      return list;
   }

   next_token();
   return list;
}

ASTTypeIdentifier::VectorPtr Module::parse_param_list()
{
   // consume (
   if( _current->type() != Token::T_LPAREN )
   {
      _errors.report( *_current ) << "Expected ( to open parameter list";
      return ASTTypeIdentifier::VectorPtr();
   }

   next_token();

   // check for an empty set
   ASTTypeIdentifier::VectorPtr list = ASTTypeIdentifier::VectorPtr( new ASTTypeIdentifier::Vector() );

   if( _current->type() != Token::T_RPAREN )
   {
      while( true )
      {
         ASTType::Ptr type = parse_type();

         if( !type )
         {
            _errors.report( *_current ) << "Couldn't parse type in parameter list";
            break;
         }

         ASTTypeIdentifier::Ptr type_id = ASTTypeIdentifier::Ptr( new ASTTypeIdentifier( type ) );
         list->push_back( type_id );

         if( _current->type() != Token::T_IDENTIFIER )
         {
            _errors.report( *_current ) << "Expected identifier after type in parameter list";
            break;
         }

         ASTIdentifier::Ptr id = ASTIdentifier::Ptr( new ASTIdentifier( *_current ) );
         type_id->set_identifier( id );
         next_token();

         // consume , or we're done
         if( _current->type() != Token::T_COMMA )
         {
            break;
         }

         next_token();
      }
   }

   // consume )
   if( _current->type() != Token::T_RPAREN )
   {
      _errors.report( *_current ) << "Expected ) to close parameter list";
      return list;
   }

   next_token();
   return list;
}

bool Module::is_binary_op()
{
   switch( _current->type() )
   {
   case Token::T_EQ:
   case Token::T_LT:
   case Token::T_GT:
   case Token::T_LE:
   case Token::T_GE:
   case Token::T_NOT:
   case Token::T_AND:
   case Token::T_OR:
   case Token::T_XOR:
   case Token::T_LSHIFT:
   case Token::T_RSHIFT:
   case Token::T_PLUS:
   case Token::T_MINUS:
   case Token::T_STAR:
   case Token::T_DIVIDE:
   case Token::T_MOD:
   case Token::T_POWER:
      return true;

   default:
      return false;
   }
}
